﻿
#region Using
using System;
using System.Linq;
using System.Security;
using MMSSync.Entities;
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Taxonomy;
using System.Windows.Forms;
using System.Xml;
using System.Collections.Generic;
using System.Drawing;
using MMSSync.Helpers;
using System.IO;
#endregion

namespace MMSSync
{
    public partial class Form1 : System.Windows.Forms.Form
    {
        #region Variables
        TreeToXMLSerializer objSerializer = new TreeToXMLSerializer();
        SecureString pwd = new SecureString();
        ClientContext clientContext;
        TaxonomySession taxSession;
        TermStore termStore;
        TermGroup termGroup;
        TermSet termSet;
        List<TreeNodeItem> lstUpdatedNodes;
        const string GuidMismatchFlag = "GuideMismatch";
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="Form1"/> class.
        /// </summary>
        public Form1()
        {
            InitializeComponent();
        }
    
        #region Events

        /// <summary>
        /// Handles the Click event of the button1 control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        /// <exception cref="InvalidOperationException">The Taxonomy Service is offline or missing</exception>
        private void btnRetrieveTS_Click(object sender, EventArgs e)
        {       
            if(string.IsNullOrEmpty(this.txtURL.Text) || string.IsNullOrEmpty(this.txtUser.Text) || string.IsNullOrEmpty(this.txtPwd.Text))    
            {
                LogMessage("Either URL, User Name or Password is missing");
            }
            else
            {
                clientContext = new ClientContext(this.txtURL.Text);
                clientContext.Credentials = new SharePointOnlineCredentials("kamal@o365dev16.onmicrosoft.com", GetPasswordSecureString("Microsoft~1"));
                taxSession = TaxonomySession.GetTaxonomySession(clientContext);

                taxSession.UpdateCache();
                clientContext.Load(taxSession, ts => ts.TermStores);
                clientContext.ExecuteQuery();

                if (taxSession.TermStores.Count == 0)
                {
                    LogMessage("No Term sotre found");
                    throw new InvalidOperationException("The Taxonomy Service is offline or missing");
                }
                else
                {
                    LogMessage("Retrieving Term Stores ...");
                    foreach (TermStore ts in taxSession.TermStores)
                    {
                        this.cbTermStores.Items.Add(new ComboboxItem { Text = ts.Name, Value = ts.Id });
                    }
                    LogMessage(string.Format("Retrieved {0} Term Stores", taxSession.TermStores.Count));
                }
            }

        }

        /// <summary>
        /// Handles the SelectedIndexChanged event of the cbTermStores control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void cbTermStores_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.cbTermGroups.Items.Clear();
            this.cbTermSets.Items.Clear();
            this.tvTermStore.Nodes.Clear();

            if (clientContext != null && taxSession != null)
            {
                string store = ((ComboboxItem)cbTermStores.SelectedItem).Text; 
                termStore = taxSession.TermStores.FirstOrDefault(t => t.Name.Equals(store));
                if(termStore != null)
                {
                    LogMessage("Retrieving Term Groups ...");
                    clientContext.Load(termStore, t=>t.Groups);
                    clientContext.ExecuteQuery();

                    foreach (TermGroup g in termStore.Groups)
                    {
                        this.cbTermGroups.Items.Add(new ComboboxItem { Text = g.Name, Value = g.Id });
                    }
                    LogMessage(string.Format("Retrieved {0} Term Groups", termStore.Groups.Count));
                }
            }
        }

        /// <summary>
        /// Handles the SelectedIndexChanged event of the cbTermGroups control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void cbTermGroups_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.cbTermSets.Items.Clear();
            this.tvTermStore.Nodes.Clear();

            if (clientContext != null && termStore != null)
            {
                string group = ((ComboboxItem)cbTermGroups.SelectedItem).Text;
                termGroup = termStore.Groups.FirstOrDefault(g => g.Name.Equals(group));

                if (termGroup != null)
                {
                    LogMessage("Retrieving Term Sets ...");
                    clientContext.Load(termGroup, t => t.TermSets);
                    clientContext.ExecuteQuery();

                    foreach (TermSet t in termGroup.TermSets)
                    {
                        this.cbTermSets.Items.Add(new ComboboxItem { Text = t.Name, Value = t.Id });
                    }
                    LogMessage(string.Format("Retrieved {0} Term Sets", termGroup.TermSets.Count));
                }
            }
        }

        /// <summary>
        /// Handles the SelectedIndexChanged event of the cbTermSets control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void cbTermSets_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.tvTermStore.Nodes.Clear();

            if (clientContext != null && termGroup != null)
            {
                // Clean up tree view
                this.tvTermStore.Nodes.Clear();

                string tset = ((ComboboxItem)cbTermSets.SelectedItem).Text;
                termSet = termGroup.TermSets.FirstOrDefault(ts => ts.Name.Equals(tset));

                TreeNode node = new TreeNodeItem { TermLabel = termSet.Name, TermGuid = termSet.Id, ToolTipText = termSet.Id.ToString()};
                node.Text = termSet.Name;
                node.ToolTipText = termSet.Id.ToString();

                if (termGroup.TermSets != null && termGroup.TermSets.Count > 0)
                {
                    LogMessage(string.Format("Retrieving Terms for {0}",termSet.Name));
                    clientContext.Load(termSet, t => t.Terms);
                    clientContext.ExecuteQuery();

                    this.AddChildTerms(clientContext, node, termSet.Terms); 
                    this.tvTermStore.Nodes.Add(node);
                    this.tvTermStore.ExpandAll();

                    LogMessage(string.Format("Retrieved {0} Terms", termSet.Terms.Count));
                }
            }
        }

        /// <summary>
        /// Handles the LinkClicked event of the lnkRefresh control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="LinkLabelLinkClickedEventArgs"/> instance containing the event data.</param>
        private void lnkRefresh_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            this.cbTermSets_SelectedIndexChanged(this, new EventArgs());
        }

        /// <summary>
        /// Handles the Click event of the btnExportTAXML control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void btnExportTAXML_Click(object sender, EventArgs e)
        {
            saveFileDialog1.Filter = "TS XML files (*.taxml)|*.taxml";
            DialogResult result = saveFileDialog1.ShowDialog();

            if (result == DialogResult.OK)
            {
                if (!string.IsNullOrEmpty(saveFileDialog1.FileName))
                {
                    KeyValuePair<string, string> kvGroup = new KeyValuePair<string, string>(termGroup.Id.ToString(), termGroup.Name);
                    objSerializer.SerializeToXML(this.tvTermStore.Nodes, kvGroup, saveFileDialog1.FileName);
                }
            }

            LogMessage(string.Format("Successfully exported to {0}", saveFileDialog1.FileName));
        }

        /// <summary>
        /// Adds the child terms.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void btnSelectFile_Click(object sender, EventArgs e)
        {
            if (this.cbTermGroups.SelectedIndex < 0 || this.cbTermSets.SelectedIndex < 0)
            {
                LogMessage("Either Term Group or Term Set not selected");
            }
            else
            {
                this.tvImportXML.Nodes.Clear();
                DialogResult result = openFileDialog1.ShowDialog();

                if (result == DialogResult.OK)
                {
                    this.txtImportFile.Text = openFileDialog1.FileName;
                    this.tvImportXML.Nodes.Clear();
                    try
                    {
                        XmlDocument doc = new XmlDocument();
                        doc.Load(this.txtImportFile.Text);

                        XmlNode xmlNode = doc.SelectSingleNode(string.Format("TermStore/TermSetGroup[@Name='{0}']/TermSet[@Name='{1}']", this.cbTermGroups.Text, this.cbTermSets.Text));                        
                        if (xmlNode == null)
                        {
                            LogMessage(string.Format("'\"{0}\" Term Set not found in the selected file", this.cbTermSets.Text));
                        }
                        else if (xmlNode.HasChildNodes)
                        {
                            LogMessage(string.Format("Loading Terms for {0} Term Set", xmlNode.Attributes["Name"].Value));

                            TreeNode parent = new TreeNodeItem { TermLabel = xmlNode.Attributes["Name"].Value, TermGuid = new Guid(xmlNode.Attributes["Id"].Value) };
                            parent.Text = xmlNode.Attributes["Name"].Value;
                            parent.ToolTipText = xmlNode.Attributes["Id"].Value;

                            this.AddChildTerms(parent, xmlNode);

                            this.tvImportXML.Nodes.Add(parent);
                            this.tvImportXML.ExpandAll();
                            LogMessage(string.Format("Completed loading Terms for \"{0}\" Term Set", xmlNode.Attributes["Name"].Value));
                        }
                        else
                        {
                            LogMessage(string.Format("{0} Term Set not found or empty", xmlNode.Attributes["Name"].Value));
                        }

                    }
                    catch (Exception ex)
                    {
                        LogMessage(string.Format("Error Retrieving Terms for file"));
                    }
                }
            }

        }

        /// <summary>
        /// Handles the Click event of the btnCompare control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void btnCompare_Click(object sender, EventArgs e)
        {
            LogMessage("Started comparisions ...");

            List<TreeNodeItem> ltn1 = new List<TreeNodeItem>();
            Helper.getallTreeNode(this.tvTermStore.Nodes, ltn1);
            List<TreeNodeItem> ltn2 = new List<TreeNodeItem>();
            Helper.getallTreeNode(this.tvImportXML.Nodes, ltn2);
            lstUpdatedNodes = new List<TreeNodeItem>();

            //find the difference
            foreach (TreeNodeItem node in ltn2)
            {
                List<TreeNodeItem> tmp = new List<TreeNodeItem>();
                foreach (TreeNodeItem tn1 in ltn1)
                {
                    //if (tn1.Parent == null && node.Parent == null && tn1.Text.Equals(node.Text) && tn1.TermGuid.Equals(node.TermGuid))
                    if (tn1.Parent == null && node.Parent == null && tn1.Text.Equals(node.Text))
                    {
                        if (tn1.TermGuid.Equals(node.TermGuid))
                            tmp.Add(node);
                        else // Set additional flag is GUID mismatch but label is same
                            node.AdditionalFlag = GuidMismatchFlag;
                    }
                    if (tn1.Parent != null && node.Parent != null)
                    {

                        //if (tn1.Text.Equals(node.Text) && tn1.Parent.Text.Equals(node.Parent.Text) && tn1.TermGuid.Equals(node.TermGuid))
                        if (tn1.Text.Equals(node.Text) && tn1.Parent.Text.Equals(node.Parent.Text))
                        {
                            if (tn1.TermGuid.Equals(node.TermGuid))
                                tmp.Add(node);
                            else // Set additional flag is GUID mismatch but label is same
                                node.AdditionalFlag = GuidMismatchFlag;
                        }
                    }

                    
                }
                if (tmp == null || tmp.Count == 0)
                {
                    lstUpdatedNodes.Add(node);
                }

                LogMessage("Comparision done");
            }
            foreach (TreeNodeItem tn in lstUpdatedNodes) //handle the difference treenode
            {
                this.tvImportXML.SelectedNode = tn;
                if (tn.AdditionalFlag != null && tn.AdditionalFlag.Equals(GuidMismatchFlag))
                {
                    // Terms with same Label but different GUID are shown as Red
                    this.tvImportXML.SelectedNode.ForeColor = Color.Red;
                    ((TreeNodeItem)this.tvImportXML.SelectedNode).TermStatus = TreeNodeItem.Status.Error;
                }
                else
                {
                    this.tvImportXML.SelectedNode.ForeColor = Color.Green;
                    ((TreeNodeItem)this.tvImportXML.SelectedNode).TermStatus = TreeNodeItem.Status.New;
                }
                this.tvImportXML.SelectedNode.Checked = true;
            }

        }

        /// <summary>
        /// Handles the Click event of the btnMerge control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void btnMerge_Click(object sender, EventArgs e)
        {
            bool flagSuccess = true;

            if (clientContext != null && this.tvImportXML.Nodes.Count > 0 && lstUpdatedNodes != null && lstUpdatedNodes.Count > 0)
            {
                foreach (TreeNodeItem tn in lstUpdatedNodes) //handle the difference treenode
                {
                    // Check if this item is checked in the TreeView
                    TreeNode tempNode =  this.tvImportXML.Nodes.Find(tn.Name, true).FirstOrDefault();
                    if (tempNode.Checked)
                    {
                        if ((((TreeNodeItem)tn.Parent).AdditionalFlag != null && ((TreeNodeItem)tn.Parent).AdditionalFlag.Equals(GuidMismatchFlag)) || (tn.AdditionalFlag != null && tn.AdditionalFlag.Equals(GuidMismatchFlag)))
                        {
                            LogMessage(string.Format("The GUID of the term \"{0}\" or its Parent is different in Target Term Set", tn.TermLabel));
                            flagSuccess = false;
                            break;
                        }
                        else
                        {
                            // TODO: Optimize ExecuteQuery calls, as of now its per change.
                            Guid parentGuid = new Guid(((TreeNodeItem)tn.Parent).TermGuid.ToString());
                            Term parentTerm = termSet.GetTerm(parentGuid);
                            Term newTerm = parentTerm.CreateTerm(tn.TermLabel, 1033, Guid.NewGuid());
                            clientContext.Load(newTerm);
                            clientContext.ExecuteQuery();
                        }
                    }
                }

                if (flagSuccess)
                {
                    LogMessage("Merge done successfully");
                }
                
            }
            else
            {
                LogMessage("Nothing is available to Import");
            }
        }
        #endregion

        #region Helper Methods
        /// <summary>
        /// Gets the password secure string.
        /// </summary>
        /// <param name="password">The password.</param>
        /// <returns></returns>
        private static SecureString GetPasswordSecureString(string password)
        {
            //Get the user's password as a SecureString
            SecureString securePassword = new SecureString();
            foreach (char c in password)
            {
                securePassword.AppendChar(c);
            }
            return securePassword;
        }

        /// <summary>
        /// Adds the child terms.
        /// </summary>
        /// <param name="clientContext">The client context.</param>
        /// <param name="parent">The parent.</param>
        /// <param name="terms">The terms.</param>
        private void AddChildTerms(ClientContext clientContext, TreeNode parent, TermCollection terms)
        {
            if (clientContext != null && terms != null)
            {
                foreach (Term t in terms)
                {
                    TreeNode newNode = new TreeNodeItem { TermLabel = t.Name, TermGuid = t.Id };
                    newNode.Text = t.Name;
                    newNode.ToolTipText = t.Id.ToString();
                    parent.Nodes.Add(newNode);

                    LogMessage(string.Format("Retrieving Terms for {0}", t.Name));
                    clientContext.Load(t.Terms);
                    clientContext.ExecuteQuery();

                    AddChildTerms(clientContext, newNode, t.Terms);
                }
            }
        }

        /// <summary>
        /// Adds the child terms.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="xmlTerms">The XML terms.</param>
        private void AddChildTerms(TreeNode parent, XmlNode xmlTerms)
        {
            if (parent != null && xmlTerms != null)
            {
                XmlNodeList nodeList = xmlTerms.ChildNodes;
                for (int i = 0; i <= nodeList.Count - 1; i++)
                {
                    XmlNode xNode = xmlTerms.ChildNodes[i];
                    Guid newGuid = (xNode.Attributes["Id"] != null && !string.IsNullOrEmpty(xNode.Attributes["Id"].Value)) ? new Guid(xNode.Attributes["Id"].Value) : Guid.Empty;
                    TreeNode newNode = new TreeNodeItem { TermLabel = xNode.Attributes["Name"].Value, TermGuid = newGuid };
                    parent.Nodes.Add(newNode);

                    LogMessage(string.Format("Retrieving Terms for {0}", xNode.Attributes["Name"].Value));
                    AddChildTerms(newNode, xNode);
                }

            }
        }

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="message">The message.</param>
        public void LogMessage(string message)
        {
            this.statusStrip.Text = message;
        }
        #endregion        
    }
}
